# -----------------------------------------------------------------
# catcoregen.pl
# Usage:
#   $ cat <header files> | catcoregen.pl > catcoretable.c
#
# This script parses the special CATALOG, DECLARE_INDEX etc. lines,
# like genbki.sh does, and generates CatCore C definitions from them.
# The structure looks like:
#
# - array of relation
#   - array of attribute
#   - array of index
# - array of type
#
# See also catcore.h for each structure definition.
# -----------------------------------------------------------------

use strict;

my $relations;
my $indexes;
my $rel;

my $processing_rel_struct = 0;

my $relstruct;

# Read input
while (my $line = <>) {

    if ($line =~ /^CATALOG.*/)
    {
	# It's the beginning of a FormData struct definition.
	# Extract the table name and OID from the CATALOG line.
	$line =~ /CATALOG\((.*),(\d+)\)(.*)$/
	    or die("malformed CATALOG line: $line\n");

	my $has_oids = (index($3, 'BKI_WITHOUT_OIDS') != -1) ? 0 : 1;
	$rel = { relname => $1,
		 reloid => $2,
		 has_oids => $has_oids };

	# We will append the lines as is to $relstruct, until we reach the
	# ending brace.
	$processing_rel_struct = 1;
	$relstruct = '';

	next;
    }

    if ($processing_rel_struct)
    {
	$relstruct = $relstruct . $line;
    }

    if ($line =~ /\}\s+Form/)
    {
	# end of struct

	$processing_rel_struct = 0;

	# Strip comments. Per the perl FAQ, this doesn't handle all C comments
	# that are legal in the language, but it's enough for our catalog
	# header files.
	$relstruct =~ s/\/\*.*?\*\///sg;

	# Strip extra whitespace, and turn all whitespace into spaces.
	$relstruct =~ s/\t/ /sg;
	$relstruct =~ s/\s\s+/ /sg;
	$relstruct =~ s/\s/ /sg;

	# Strip { } at beginning and end
	$relstruct =~ /{(.*)}/;
	$relstruct = $1;


	# split at ; to columns
	my @cols = split (/;/, $relstruct);

	# there's an empty element at the end, because of the last ';'
	pop @cols;

	$rel->{attributes} = \@cols;

	$relations->{$rel->{relname}} = $rel;
	next;
    }

    if ($line =~ /^DECLARE_(UNIQUE_)?INDEX/)
    {
	if ($line =~ /^DECLARE_(UNIQUE_)?INDEX\((\w+),\s*(\d+),\s*on\s+(\w+)\s+using\s*(\w+)\s*\((.*)\)\);/)
	{
	    my $indexdef = {
		indexname => $2,
		indexoid => $3,
		amname => $5
	    };
	    my $relname = $4;

	    @{$indexdef->{cols}} = split (/,/, $6);

	    push(@{$indexes->{$relname}}, $indexdef);
	}
	else {
	    die("malformed DECLARE_UNIQUE_INDEX or DECLARE_INDEX line: $line\n");
	}
    }
}

# Ok, we've read all the data. Start printing!
#
# Constant header first

print <<"END_MESSAGE";
/*-------------------------------------------------------------------------
 *
 * catcoretable.c
 *	  Auto-generated C file from the catalog headers. Don't edit manually.
 *
 * WARNING: DO NOT MODIFY THIS FILE:
 * Generated by ./catcoregen.pl
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"
#include "catalog/catcore.h"
#include "catalog/catalog.h"
#include "catalog/gp_configuration.h"
#include "catalog/gp_fastsequence.h"
#include "catalog/gp_id.h"
#include "catalog/gp_policy.h"
#include "catalog/gp_san_config.h"
#include "catalog/gp_segment_config.h"
#include "catalog/gp_verification_history.h"
#include "catalog/gp_version.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_appendonly.h"
#include "catalog/pg_appendonly_alter_column.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_autovacuum.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
#include "catalog/pg_extprotocol.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_filespace.h"
#include "catalog/pg_filespace_entry.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_listener.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_pltemplate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_resqueue.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_user_mapping.h"
#include "catalog/pg_window.h"
#include "catalog/pg_tidycat.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"

/*
 * First, information about every datatype that is used in catalogs.
 * (This is maintained manually in catcoregen.pl, there are so many of these
 * and they change seldom.)
 */
static const CatCoreType CatCoreType_bool =
	{BOOLOID, F_BOOLEQ, F_BOOLLT, F_BOOLLE, F_BOOLGE, F_BOOLGT};
static const CatCoreType CatCoreType_bytea =
	{BYTEAOID, F_BYTEAEQ, F_BYTEALT, F_BYTEALE, F_BYTEAGE, F_BYTEAGT};
static const CatCoreType CatCoreType_char =
	{CHAROID, F_CHAREQ, F_CHARLT, F_CHARLE, F_CHARGE, F_CHARGT};
static const CatCoreType CatCoreType_NameData =
	{NAMEOID, F_NAMEEQ, F_NAMELT, F_NAMELE, F_NAMEGE, F_NAMEGT};
static const CatCoreType CatCoreType_int8 =
	{INT8OID, F_INT8EQ, F_INT8LT, F_INT8LE, F_INT8GE, F_INT8GT};
static const CatCoreType CatCoreType_int2 =
	{INT2OID, F_INT2EQ, F_INT2LT, F_INT2LE, F_INT2GE, F_INT2GT};
static const CatCoreType CatCoreType_int2vector =
	{INT2VECTOROID, InvalidOid, InvalidOid, InvalidOid, InvalidOid, InvalidOid};
static const CatCoreType CatCoreType_int4 =
	{INT4OID, F_INT4EQ, F_INT4LT, F_INT4LE, F_INT4GE, F_INT4GT};
static const CatCoreType CatCoreType_regproc =
	{REGPROCOID, F_OIDEQ, F_OIDLT, F_OIDLE, F_OIDGE, F_OIDGT};
static const CatCoreType CatCoreType_text =
	{TEXTOID, F_TEXTEQ, F_TEXT_LT, F_TEXT_LE, F_TEXT_GE, F_TEXT_GT};
static const CatCoreType CatCoreType_Oid =
	{OIDOID, F_OIDEQ, F_OIDLT, F_OIDLE, F_OIDGE, F_OIDGT};
static const CatCoreType CatCoreType_tid =
	{TIDOID, F_TIDEQ, F_TIDLT, F_TIDLE, F_TIDGE, F_TIDGT};
static const CatCoreType CatCoreType_TransactionId =
	{XIDOID, InvalidOid, InvalidOid, InvalidOid, InvalidOid, InvalidOid};
static const CatCoreType CatCoreType_oidvector =
	{OIDVECTOROID, F_OIDVECTOREQ, F_OIDVECTORLT, F_OIDVECTORLE, F_OIDVECTORGE, F_OIDVECTORGT};
static const CatCoreType CatCoreType_float4 =
	{FLOAT4OID, F_FLOAT4EQ, F_FLOAT4LT, F_FLOAT4LE, F_FLOAT4GE, F_FLOAT4GT};
static const CatCoreType CatCoreType_char_array =
	{1002, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_int2_array =
	{INT2ARRAYOID, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_int4_array =
	{INT4ARRAYOID, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_text_array =
	{TEXTARRAYOID, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_float4_array =
	{FLOAT4ARRAYOID, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_Oid_array =
	{1028, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_aclitem_array =
	{1034, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};
static const CatCoreType CatCoreType_time =
	{TIMEOID, F_TIME_EQ, F_TIME_LT, F_TIME_LE, F_TIME_GE, F_TIME_GT};
static const CatCoreType CatCoreType_timestamptz =
	{TIMESTAMPTZOID, F_TIMESTAMP_EQ, F_TIMESTAMP_LT, F_TIMESTAMP_LE, F_TIMESTAMP_GE, F_TIMESTAMP_GT};
static const CatCoreType CatCoreType_gpxlogloc =
	{XLOGLOCOID, F_GPXLOGLOCEQ, F_GPXLOGLOCLT, F_GPXLOGLOCLE, F_GPXLOGLOCGE, F_GPXLOGLOCGT};
static const CatCoreType CatCoreType_anyarray =
	{2277, F_ARRAY_EQ, F_ARRAY_LT, F_ARRAY_LE, F_ARRAY_GE, F_ARRAY_GT};

/* A pseudo-attr entry, for the "oid" system column */
const CatCoreAttr TableOidAttr = {
    "oid", ObjectIdAttributeNumber, &CatCoreType_Oid
};

END_MESSAGE


# Helper function for forming the attribute arrays. The arguments are the
# relation and attribute name, and this generates a reference to the correct
# Anum_* macro, for that column.
sub attname_to_anum
{
    my ($relname, $attname) = @_;

    # For most columns, the mapping is straightforward, but there are a few
    # Greenplum-specific catalogs that break the normal naming convention.
    if ($relname eq 'gp_distribution_policy')
    {
	$relname = 'gp_policy';
    }
    elsif ($relname eq 'pg_stat_last_operation')
    {
	$relname = 'pg_statlastop';
    }
    elsif ($relname eq 'pg_stat_last_shoperation')
    {
	$relname = 'pg_statlastshop'
    }

    if ($attname eq "oid")
    {
	return "ObjectIdAttributeNumber";
    }

    return "Anum_" . $relname . "_" . $attname;
}

# Helper function for generating a reference to the CatCoreType_* struct,
# for a given C type.
sub ctype_to_coretype
{
    my ($ctype, $isarray) = @_;
    my $result;

    $result = "CatCoreType_" . $ctype;

    if ($isarray ne '')
    {
	$result = $result . "_array";
    }
    return $result;
}


# Sort the list of relations, by relation name. This makes it possible to
# binary search the array.
my @relations = sort {$a->{relname} cmp $b->{relname}} (values %{$relations});

# First generate the attribute array for each relation
print "/* Attributes for each relation (auto-generated from *.h files) */\n";
foreach $rel (@relations) {
    print "static const CatCoreAttr $rel->{relname}Attributes[] = {\n";

    my @cols = @{$rel->{attributes}};

    my $attnum = 1;
    foreach my $col (@cols) {
	my ($ctype, $attname, $isarray) = ($col =~ /(\w+)\s+(\w+)(\[?)/);
	print "\t{\"$attname\", $attnum, &" . ctype_to_coretype($ctype, $isarray) . "}";
	if ($attnum != scalar @cols)
	{
	    print ",";
	}
	print "\n";
	$attnum++;
    }
    print "};\n";
}
print "\n";

# Then index array for each relation
print "/* Indexes for each relation (auto-generated from *.h files) */\n";
foreach $rel (@relations) {
    print "static const CatCoreIndex $rel->{relname}Indexes[] = ";

    my $indref = $indexes->{$rel->{relname}};
    if (!$indref)
    {
	print "{ };\n";
	next;
    }

    my @indexes = @{$indexes->{$rel->{relname}}};
    my $indexnum = 1;
    print "{\n";
    foreach my $indexdef (@indexes) {

	print "\t/* $indexdef->{indexname} */\n";
	print "\t{" . $indexdef->{indexoid} . ", {\n";

	my @cols = @{$indexdef->{cols}};
	
	for (my $i=0; $i <= 3; $i++) {
	    my $col = $cols[$i];

	    if ($col)
	    {
		# trim whitespace
		$col =~ s/^\s+|\s+$//g;

		my ($colname, $ops) = split(/ /, $col);
		print "\t\t" . attname_to_anum($rel->{relname}, $colname) . " /* $rel->{relname} $ops */";
	    }
	    else
	    {
		print "\t\tInvalidAttrNumber";
	    }
	    if ($i < 3)
	    {
		print ",";
	    }
	    print "\n";
	}
	
	print "\t}, " . scalar(@cols) . "}";
	
	my ($ctype, $attname) = ($indexdef =~ /(\w+)\s+(\w+)/);

	if ($indexnum != scalar @indexes)
	{
	    print ",";
	}
	print "\n";
	$indexnum++;
    }
    print "};\n";
}
print "\n";

# Finally, the relations array itself.
my $nrelations = scalar @relations;
my $lastreloid = $relations[-1]->{reloid};

print "/* List of catalog relations (auto-generated from *.h files) */\n";
print "const int CatCoreRelationSize = $nrelations;\n";
print "const CatCoreRelation CatCoreRelations[$nrelations] = {\n";
foreach $rel (@relations) {
    print "\t{";
    print "\"$rel->{relname}\", ";
    print "$rel->{reloid}, ";
    print "$rel->{relname}Attributes, lengthof($rel->{relname}Attributes), ";
    print "$rel->{relname}Indexes, lengthof($rel->{relname}Indexes), ";
    print $rel->{has_oids} ? "true" : "false";
    print "}";
    if ($rel->{reloid} != $lastreloid)
    {
	print ",";
    }
    print "\n";
}
print "};\n";
